package org.yylnb.openplatform.gateway.filter;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;
import org.yylnb.openplatform.commons.constans.ApiTagInRedis;
import org.yylnb.openplatform.commons.utlis.CommonsResult;
import org.yylnb.openplatform.gateway.api.CacheService;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;

/**
 * 接口限流
 *
 * @author RainLin
 * @date 2020/7/25 - 19:48
 */
@Component
public class LimitFilter extends ZuulFilter {

    private static final Logger LOGGER = LoggerFactory.getLogger(LimitFilter.class);

    private final ObjectMapper objectMapper;

    private final CacheService cacheService;

    public LimitFilter(CacheService cacheService, ObjectMapper objectMapper) {
        this.cacheService = cacheService;
        this.objectMapper = objectMapper;
    }

    @Override
    public String filterType() {
        //前置过滤器
        return FilterConstants.PRE_TYPE;
    }

    /**
     * 我们在这个过滤器中根据当前的app访问的接口来进行判断,所以必须要保证用户输入了相应的参数,但是签名啊之类是否符合对我们来说没有那么高的要求
     * 所以这个过滤器的优先级和签名等同级别的,谁先谁后没区别
     *
     * @return
     */
    @Override
    public int filterOrder() {
        return 70;
    }

    @Override
    public boolean shouldFilter() {
        RequestContext context = RequestContext.getCurrentContext();
        //前置过滤器可能会拦截请求,所以以是否拦截为准
        return context.sendZuulResponse();
    }

    @Override
    public Object run() throws ZuulException {
        //我们要判断当前app访问的这个接口是否在限流范围内
        RequestContext context = RequestContext.getCurrentContext();
        HttpServletRequest request = context.getRequest();
        //当前app
        String app_key = request.getParameter("app_key");
        //当前的接口
        String method = request.getParameter("method");


        //我们以统一的前缀加上app_key加上接口名作为key,以当前时间作为分数,随便保存一个值到zset中,每次请求的时候判断下当前时间和我们要求的限流时间内的数据个数
        long max = System.currentTimeMillis();
        //根据我们的要求,一分钟的最小是应该是当前时间减去6000
        CommonsResult<List<String>> resultAboutLimitInRedis = cacheService.zRangeByScore(ApiTagInRedis.API_LIMIT + app_key + method, max - 60000, max);
        //获取符合的请求
        List<String> data = resultAboutLimitInRedis.getData();
        CommonsResult result = null;
        if (data.size() == 1) {
            //已经有一次了,不能发起请求
            LOGGER.error("一分钟的阈值已经到了");
            result = CommonsResult.ERROR.msg("一分钟只允许发起一次请求");

        } else {
            LOGGER.error("一分钟的阈值还没到,应该判断一小时的");
            resultAboutLimitInRedis = cacheService.zRangeByScore(ApiTagInRedis.API_LIMIT + app_key + method, max - 60000 * 60, max);
            data = resultAboutLimitInRedis.getData();
            if (data.size() == 5) {
                //一小时的阈值已经到达
                result = CommonsResult.ERROR.msg("一小时执行允许五次请求");
            } else {
                //可以放行了,但是保存一下这个请求时间
                cacheService.zAdd(ApiTagInRedis.API_LIMIT + app_key + method, "本接口限流！", max);
                return null;
            }
        }

        //如果代码到这里,代表应该拦截请求
        context.setSendZuulResponse(false);
        HttpServletResponse response = context.getResponse();
        response.setContentType("application/json;charset=utf-8");
        try {
            context.setResponseBody(objectMapper.writeValueAsString(result));
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }

        return null;
    }
}

